/*
============================================================================
Name        : dd_http.c
Author      : Jian (raphyer@hotmail.com)
Version     :
Copyright   : GPL
Description : dd_http.c - source file
============================================================================
*/

#include "dd_http.h"
#include "dd_log.h"
#include "dd_vector.h"
#include "dd_util.h"
#include "dd_zip.h"

#include "sd_platform.h"

#define __HTTP_BUF_LEN               (1024 * 8)
#define __HTTP_READ_BUF_LEN          __HTTP_BUF_LEN
#define __HTTP_UNZIP_BUF_LEN         (__HTTP_READ_BUF_LEN * 2)
#define __HTTP_CHUNK_HEADER_MAX_LEN  200

#define __HTTP_CHECK_CANCEL(http, r) do {if (((http)->callback.is_cancel) && ((http)->callback.is_cancel(http))) {ret = EHttpCancel; r;}} while(0)


static const char* _reqString[EHttpReqCount] = {"GET", "POST", "HEAD"} ;


typedef struct {
    char* key ;
    char* value ;
} dd_http_header;

typedef struct __dd_post_fun dd_post_fun ;

struct __dd_post_fun {
    int  (*len)(dd_post_fun* body) ;
    int  (*get_data)(dd_post_fun* body, char buf[__HTTP_BUF_LEN]) ;
    void (*reset)(dd_post_fun* body) ;
    void (*destroy)(dd_post_fun* body) ;
} ;

typedef struct {
    dd_post_fun  vfun;
    const char*  buffer ;
    int          len ;
    int          offset ;
} dd_post_buffer;

typedef struct {
    dd_post_fun  vfun ;
    sd_file*     file ;
    int          len ;
} dd_post_file;

static int _post_buf_len(dd_post_fun* body) 
{
    dd_post_buffer* data = (dd_post_buffer*)body ;
    __DD_ASSERT_DEFENSE(data, return -1) ;

    return data->len ;
}

static int _post_buf_get_data(dd_post_fun* body, char buf[__HTTP_BUF_LEN]) 
{
    dd_post_buffer* data = (dd_post_buffer*)body ;
    int             ret = 0 ;

    __DD_ASSERT_DEFENSE(data, return -1) ;

    ret = ((data->offset + __HTTP_BUF_LEN) <= data->len) ?  __HTTP_BUF_LEN : (data->len - data->offset) ;
    
    __DD_ASSERT_DEFENSE(ret >= 0, return -1) ;
    if (ret == 0)  return 0 ;
    
    memcpy(buf, data->buffer, ret) ;
    data->offset += ret ;

    return ret ;
}

static void _post_buf_reset(dd_post_fun* body)
{
    dd_post_buffer* data = (dd_post_buffer*)body ;
    __DD_ASSERT_DEFENSE(data, return) ;

    data->offset = 0 ;
}

static void _post_buf_destroy(dd_post_fun* body) 
{
    dd_post_buffer* data = (dd_post_buffer*)body ;
    __DD_ASSERT_DEFENSE(data, return) ;

    dd_free(data) ;
}

static dd_post_fun* _create_post_buffer(const char* buffer, int len) 
{
    dd_post_buffer* ret = NULL;
    
    __DD_ASSERT_DEFENSE(buffer && len > 0, return NULL) ;

    ret = dd_malloc(sizeof(dd_post_buffer)) ;
    __DD_CHECK_MEM(ret, sizeof(dd_post_buffer), return NULL) ;
    
    ret->vfun.len       = _post_buf_len ;
    ret->vfun.get_data  = _post_buf_get_data ;
    ret->vfun.reset     = _post_buf_reset ;
    ret->vfun.destroy   = _post_buf_destroy ;
    
    ret->buffer = buffer ;
    ret->len    = len ;
    ret->offset = 0 ;

    return &(ret->vfun) ;
}


static int _post_file_len(dd_post_fun* body) 
{
    dd_post_file* data = (dd_post_file*)body ;
    __DD_ASSERT_DEFENSE(data, return -1) ;

    return data->len ;
}

static int _post_file_get_data(dd_post_fun* body, char buf[__HTTP_BUF_LEN]) 
{
    dd_post_file* data = (dd_post_file*)body ;
    int           ret  = 0 ;
    int           ofsset = 0 ; 

    __DD_ASSERT_DEFENSE(data && data->file, return -1) ;

    ofsset = sd_file_tell(data->file) ;

    ret = ((ofsset + __HTTP_BUF_LEN) <= data->len) ?  __HTTP_BUF_LEN : (data->len - ofsset) ;

    __DD_ASSERT_DEFENSE(ret >= 0, return -1) ;
    if (ret == 0)  return 0 ;
    
    if (sd_file_read(data->file, buf, ret) != ret) {
        dd_log_error("failed to read post file") ;
        return EHttpReadPostFileError ;
    }
    
    return ret ;
}

static void _post_file_reset(dd_post_fun* body)
{
    dd_post_file* data = (dd_post_file*)body ;
    __DD_ASSERT_DEFENSE(data, return) ;

    sd_file_seek(data->file, 0, DD_SEEK_SET) ;
}


static void _post_file_destroy(dd_post_fun* body) 
{
    dd_post_file* data = (dd_post_file*)body ;
    __DD_ASSERT_DEFENSE(data, return) ;

    if (data->file) 
        sd_file_close(data->file) ;
    dd_free(data) ;
}


static dd_post_fun* _create_post_file(const DD_UTF8* fileName) 
{
    dd_post_file* ret = NULL;

    __DD_ASSERT_DEFENSE(fileName, return NULL) ;

    ret = dd_malloc(sizeof(dd_post_file)) ;
    __DD_CHECK_MEM(ret, sizeof(dd_post_file), return NULL) ;

    ret->vfun.len       = _post_file_len ;
    ret->vfun.get_data  = _post_file_get_data ;
    ret->vfun.reset     = _post_file_reset ;
    ret->vfun.destroy   = _post_file_destroy ;
    
    if (sd_get_file_size(fileName, (DD_UINT*)&ret->len) < 0 || ret->len <= 0) {
        dd_log_error("failed to get size for post file: %s", fileName) ;
        goto _error ;
    }

    ret->file = sd_file_open(fileName, DD_FILE_READ) ;
    if (!ret->file) {
        dd_log_error("failed to read post file: %s", fileName) ;
        goto _error ;
    }
    
    return &(ret->vfun) ;
    
    _error:
    if (ret)  _post_file_destroy(&ret->vfun) ;
    return NULL ;
}

static char* _new_lower_str(const char* str)
{
    char* cpyStr = NULL ;
    char* ret = NULL ;

    cpyStr = dd_newstr(str) ;
    __DD_ASSERT_DEFENSE(cpyStr, return NULL) ;
    
    ret = dd_str_tolower(cpyStr) ;
    __DD_ASSERT_DEFENSE(ret, goto _end) ;
    
    dd_str_trim(ret) ;

    _end:
    dd_destroy(cpyStr) ;
    return ret ;
}

static void _add_header(dd_vector* vector, const char* key, const char* val) 
{
    dd_http_header* header = NULL;
    
    __DD_ASSERT_DEFENSE(vector && key && val, return) ;

    header = (dd_http_header*)dd_malloc(sizeof(dd_http_header)) ;
    __DD_CHECK_MEM(header, sizeof(dd_http_header), return) ;

    header->key   = dd_str_trim(dd_newstr(key)) ;
    header->value = _new_lower_str(val) ;

    dd_vector_append(vector, header) ;
}

static int _create_headers(dd_http* http, const dd_http_range* range) 
{
    if (!http->reqHeaders)
        http->reqHeaders = dd_vector_create(16) ;

    __DD_ASSERT_DEFENSE(http->reqHeaders, return -1) ;

    _add_header(http->reqHeaders, "Host", http->host) ;
    _add_header(http->reqHeaders, "User-Agent", "dd_http") ;
    _add_header(http->reqHeaders, "Accept", "*/*") ;
    // _add_header(http->reqHeaders, "Accept-Encoding", "gzip") ;
    _add_header(http->reqHeaders, "Cache-Control", "no-cache") ;

    if (http->reqType == EHttpPost) {
        char lenBuf[10 +1] = {0} ;
        sprintf(lenBuf, "%d", http->postDataFun->len(http->postDataFun)) ;
        _add_header(http->respHeaders, "Content-Length", lenBuf) ;
    }
    
    if (range) {
        char valBuf[30 + 1] = {0} ;
        
        __DD_ASSERT_DEFENSE((range->start >= 0) && ((range->start < range->end) || (range->end == -1)), return EHttpInvalidRange) ;
        
        if (range->end != -1) 
            sprintf(valBuf, "bytes=%d-%d", range->start, range->end) ;
        else
            sprintf(valBuf, "bytes=%d-", range->start) ;
        
        _add_header(http->reqHeaders, "Range", valBuf) ;
        _add_header(http->reqHeaders, "Accept-Ranges", "bytes") ;
    }

    _add_header(http->reqHeaders, "Connection", "keep-alive") ;

    return 0 ;
}

static int _parse_url(dd_http* http, const DD_UTF8* url) 
{
    char*       lowerUrl  = NULL ; 
    const char* hostStart = NULL ;
    const char* addrStart = NULL ;

    __DD_ASSERT_DEFENSE(url, return -1) ;
    
    lowerUrl = dd_str_tolower((const char*)url) ;
    __DD_ASSERT_DEFENSE(lowerUrl, return -1) ;

    hostStart = lowerUrl ;
    if (strstr(lowerUrl, "http://"))
        hostStart += strlen("http://") ;

    addrStart = strstr(hostStart, "/") ;
    if (addrStart) {
        if (addrStart - hostStart <= 0 ) {
            dd_log_error("host is null for url: %s", lowerUrl) ;
            return EHttpInvalidUrl ;
        }

        http->host    = dd_newstr_with_len(hostStart, addrStart - hostStart) ;
        http->address = dd_newstr(addrStart) ;
    } else {
        addrStart = "/" ;

        if (strlen(hostStart) <= 0) {
            dd_log_error("host is null for url: %s", lowerUrl) ;
            return EHttpInvalidUrl ;
        }

        http->host    = dd_newstr(hostStart) ;
        http->address = dd_newstr(addrStart) ;
    }

    return 0 ;
}

static int _send_header(dd_http* http)
{
    int ret = -1 ;
    int i = 0 ;
    int sendLen = 0 ;

    http->buf[0] = 0 ;

    sprintf(http->buf, "%s %s HTTP/1.1\r\n", _reqString[http->reqType], http->address) ;

    for (; i < dd_vector_count(http->reqHeaders); i++) {
        dd_http_header* header = dd_vector_get(http->reqHeaders, i) ;

        __DD_ASSERT_DEFENSE(header && header->key && header->value, return -1) ;
        
        strncat(http->buf, header->key, __HTTP_BUF_LEN) ;
        strncat(http->buf, ":", __HTTP_BUF_LEN) ;
        strncat(http->buf, header->value, __HTTP_BUF_LEN) ;
        strncat(http->buf, "\r\n", __HTTP_BUF_LEN) ;
    }

    strncat(http->buf, "\r\n", __HTTP_BUF_LEN) ;

    sendLen = strlen(http->buf) ;
    return (sd_tcp_send(http->tcp, http->buf, sendLen) == sendLen) ? 0 : -1 ;
}

static int _send_post_body(dd_http* http) 
{
    int sendLen = 0 ;

    if (http->reqType != EHttpPost)
        return 0 ;

    while(1) {
        
        int ret = http->postDataFun->get_data(http->postDataFun, http->buf) ;
        
        if (ret == 0) break ;
        if (ret < 0)  return -1 ;
        
        if (sd_tcp_send(http->tcp, http->buf, ret) != ret) 
            return -1 ;
        
        sendLen += ret ;
    }
    
    __DD_ASSERT_DEFENSE(sendLen == http->postDataFun->len(http->postDataFun), return -1) ;
    return 0 ;
}

static char* _split(char* str, char* data[], int sepCount) 
{
    char* ret = NULL ;
    int   i = 0 ;
    int   sepLen = 0 ;
    
    for (; i < sepCount; i++) {
        char* find = strstr(str, data[i]) ;
        if (!find) return NULL ;

        sepLen = strlen(data[i])  ;
        data[i] = str ;
        str = find + sepLen;
        *find = 0 ;
        ret = str ;
    }

    return ret ;
}

static int _recv_header(dd_http* http) 
{
    int   ret = - 1;
    char* headerEnd = NULL ;
    char* loop = NULL ;
    char* tmpData[3] = {0} ;

    http->recvBufLen = 0 ;

    while(1) {
        
        if (__HTTP_BUF_LEN - http->recvBufLen <= 0) {
            dd_log_error("recv header error for the recvBufLen is reach to __HTTP_BUF_LEN, but still not get the header end char yet") ;
            return -1 ;
        }

        ret = sd_tcp_recv(http->tcp, http->buf + http->recvBufLen, __HTTP_BUF_LEN - http->recvBufLen) ;
        if (ret <= 0) {
            dd_log_error("sd_tcp_recv to got header return error: %d", ret) ;
            return -1 ;
        }

        http->recvBufLen += ret ;
        
        headerEnd = strstr(http->buf, "\r\n\r\n") ;
        if (headerEnd)  break ;
    }
    
    *(headerEnd + 2) = 0 ;

    /* parser HTTP/1.1 200 OK */
    tmpData[0] = " " ;tmpData[1] = " " ; tmpData[2] = "\r\n" ;
    if ((loop = _split(http->buf, tmpData, 3)) == NULL) {
        dd_log_error("parser status code error") ;
        return -1;
    }
    
    if (strcmp(tmpData[0], "HTTP/1.1") != 0 && strcmp(tmpData[0], "HTTP/1.0") != 0) {
        dd_log_error("error that http version: %s is not http 1.1 or 1.0", tmpData[0]) ;
        return -1;
    }
    
    if (memcmp(tmpData[1], "200", 3) != 0 && memcmp(tmpData[1], "204", 3) != 0 && memcmp(tmpData[1], "206", 3) != 0
        && memcmp(tmpData[1], "301", 3) != 0 && memcmp(tmpData[1], "302", 3) != 0) {
        dd_log_error("http got invalid status code: %s", tmpData[1]) ;
        return -1;
    }
    
    http->respCode = dd_str_trim(dd_newstr(tmpData[1])) ;
    __DD_ASSERT_DEFENSE(http->respCode, return -1) ;

    /* parser headers */
    if (!http->respHeaders)
        http->respHeaders = dd_vector_create(24) ;
    __DD_ASSERT_DEFENSE(http->respHeaders, return -1) ;

    while(*loop != 0) {
        tmpData[0] = ":"; tmpData[1] = "\r\n" ;
        loop = _split(loop, tmpData, 2) ;
        if (!loop) {
            dd_log_error("parser header error") ;
            return -1;
        }
        _add_header(http->respHeaders, tmpData[0], tmpData[1]) ;    
    }

    http->recvBufLen = http->recvBufLen - (headerEnd + 4 - http->buf) ; /* 4 is len of "\r\n\r\n" */ 
    memmove(http->buf, headerEnd + 4, http->recvBufLen) ;
    return 0 ;
} 

static int _parser_range_offset(dd_http* http) 
{
    const char* rangeVal    = dd_http_get_resp_header(http, "Content-Range") ;
    char*       startOffset = NULL ;
    int         offset = 0 ;

    if (!rangeVal) return 0 ;
    
    startOffset = dd_str_split(rangeVal, "bytes ", "-");
    if (!startOffset)  
        goto _error ;

    if (dd_atoi(startOffset, &offset) < 0 || offset < 0) 
        goto _error ;
    
    return offset ;

    _error:
    dd_log_error("parser range offset error, range value: %s", rangeVal) ;
    return -1 ;
}


static int _process_body_data(dd_http* http, const char* buf, int* offset, int len, int isZip) 
{
    dd_log_trace("process body data offset: %d, len: %d", *offset, len) ;

    if (isZip) {
        int srcLen   = len ;
        int unzipLen = __HTTP_UNZIP_BUF_LEN ;
        
        while(srcLen > 0) {
            if (dd_zip_ungzip(http->zip, buf + (len - srcLen), &srcLen, http->unzipBuf, &unzipLen) < 0) {
                dd_log_error("process body data dd_zip_ungzip got error") ;
                return -1 ;
            }

            if (http->callback.process_body && http->callback.process_body(http, http->unzipBuf, *offset, unzipLen) < 0) 
                return -1;

            *offset += unzipLen ;
        }
        
        *offset -= len ;

    } else {
        if (http->callback.process_body) 
            return http->callback.process_body(http, buf, *offset, len) ;
    }

    return 0 ;
}

static int _recv_content(dd_http* http, int contentLen, int* offset, int isZip, int isChunked) 
{
    int         ret        = -1 ;
    int         bufLen     = 0 ;
    int         readedLen  = 0 ;

    if (http->recvBufLen > 0) {

        if (isChunked && http->recvBufLen > contentLen) {
            if (_process_body_data(http, http->buf, offset, contentLen, isZip) < 0) 
                goto _end ;

            http->recvBufLen -= contentLen ;
            memmove(http->buf, http->buf + contentLen, http->recvBufLen) ;
            *offset += contentLen ;
            return 0 ;
        } 
        
        __DD_ASSERT_DEFENSE(http->recvBufLen <= contentLen, return -1) ;

        if (_process_body_data(http, http->buf, offset, http->recvBufLen, isZip) < 0) 
            goto _end ;

        if (http->recvBufLen == contentLen) {
            ret = 0 ;
            goto _end ;
        }

        *offset += http->recvBufLen ;
        contentLen -= http->recvBufLen ;
    }

    while(contentLen > 0) {
        bufLen = (contentLen < __HTTP_READ_BUF_LEN) ? contentLen : __HTTP_READ_BUF_LEN ;

        readedLen = sd_tcp_recv(http->tcp, http->buf, bufLen) ;
        if (readedLen <= 0) {
            dd_log_error("sd_tcp_recv to got body return error: %d", ret) ;
            goto _end ;
        }

        if (_process_body_data(http, http->buf, offset, readedLen, isZip) < 0) 
            goto _end ;

        contentLen -= readedLen ;
        *offset += readedLen ;

        __HTTP_CHECK_CANCEL(http, goto _end) ;
    }
    
    ret = 0 ;

    _end:
    http->recvBufLen = 0 ;
    return ret ;
}

static int _create_gzip_data(dd_http* http) 
{ 
    if (!http->unzipBuf) {
        http->unzipBuf = dd_malloc(__HTTP_UNZIP_BUF_LEN) ;
        __DD_ASSERT_DEFENSE(http->unzipBuf, return -1) ;
    }
    
    if (!http->zip) {
        http->zip = dd_zip_create() ;
        __DD_ASSERT_DEFENSE(http->zip, return -1) ;
    } else {
        dd_zip_reset(http->zip) ;
    }

    return 0 ;
}

static int _recv_normal_body(dd_http* http)
{
    int         contentLen = 0 ;
    int         offset     = 0 ;
    const char* gzipVal    = dd_http_get_resp_header(http, "Content-Encoding") ;

    if (gzipVal && strcmp(gzipVal, "gzip") != 0) {
        dd_log_error("http not support encoding: %s", gzipVal) ;
        return -1 ;
    }

    if (gzipVal && _create_gzip_data(http) < 0) 
        return -1 ;

    if (dd_atoi(dd_http_get_resp_header(http, "Content-Length"), &contentLen) < 0 || contentLen <= 0) {
        dd_log_error("error to get content length from header: %s", dd_http_get_resp_header(http, "Content-Length")) ;
        return -1 ;
    }

    if ((offset = _parser_range_offset(http)) < 0) 
        return -1 ;

    return _recv_content(http, contentLen, &offset, gzipVal != NULL, 0) ;
}

static int _recv_chunk_len(dd_http* http) 
{
    int         ret = -1;
    int         readedLen = -1 ;
    const char* sep = NULL ;

    while(1) {
        if (http->recvBufLen > 0) {
            http->buf[http->recvBufLen] = 0 ;
            sep = strstr(http->buf, "\r\n") ;
            
            if (sep) {
                if ((ret = dd_str_hex2i(http->buf)) < 0) 
                    dd_log_error("get error http chunk header: %s", http->buf) ;
                
                http->recvBufLen -= (sep + 2 - http->buf) ;
                memmove(http->buf, sep + 2, http->recvBufLen) ;
                return ret;
            }

            if (http->recvBufLen > __HTTP_CHUNK_HEADER_MAX_LEN) {
                dd_log_error("parser header error that buffer length is exceed than %d, but still do not get the chunk header", __HTTP_CHUNK_HEADER_MAX_LEN) ;
                return -1 ;
            }
        }

        readedLen = sd_tcp_recv(http->tcp, http->buf + http->recvBufLen, __HTTP_CHUNK_HEADER_MAX_LEN) ;
        if (readedLen <= 0) {
            dd_log_error("sd_tcp_recv to got chunk header return error: %d", ret) ;
            return -1 ;
        }

        http->recvBufLen += readedLen ;
    }

    __DD_ASSERT_DEFENSE(0, return -1) ;
}

static int _eat_chunk_end(dd_http* http)
{
    int ret = -1 ;
    
    _again:

    dd_log_trace("eat chunk end, remain http->recvBufLen: %d", http->recvBufLen) ;

    /* eat the last "\r\n" */
    if (http->recvBufLen >= 2) {
        __DD_ASSERT_DEFENSE(memcmp(http->buf, "\r\n", 2) == 0, return -1) ;
        http->recvBufLen -= 2 ;
        if (http->recvBufLen > 0) 
            memmove(http->buf, http->buf + 2, http->recvBufLen) ;
        return 0 ;
    }
    
    if ((ret = sd_tcp_recv(http->tcp, http->buf + http->recvBufLen, __HTTP_READ_BUF_LEN - http->recvBufLen)) <= 0) {
        dd_log_error("sd_tcp_recv error to eat chunk end: %d", ret) ;
        return -1 ;
    }
    
    http->recvBufLen += ret ;
    __HTTP_CHECK_CANCEL(http, return -1) ;
    goto _again ;

    return 0 ;
}

static int _recv_chunked_body(dd_http* http) 
{
    int         ret       = -1 ;
    int         offset    = -1 ;
    int         chunkLen  = 0 ;
    int         readedLen = 0 ;
    const char* gzipVal   = dd_http_get_resp_header(http, "Content-Encoding") ;

    if (gzipVal && strcmp(gzipVal, "gzip") != 0) {
        dd_log_error("http not support encoding: %s", gzipVal) ;
        return -1 ;
    }

    if (gzipVal && _create_gzip_data(http) < 0) 
        return -1 ;

    if ((offset = _parser_range_offset(http)) < 0) 
        goto _end ;

    while(1) {
        
        /* get chunk length */ 
        chunkLen = _recv_chunk_len(http) ;

        if (chunkLen < 0) 
            goto _end ;

        if (chunkLen == 0) {
            ret = 0 ;
            goto _end ;
        }

        if (_recv_content(http, chunkLen, &offset, gzipVal != NULL, 1) < 0)
            goto _end ;

        if (_eat_chunk_end(http) < 0) 
            goto _end ;
    }

    _end:
    http->recvBufLen = 0 ;
    return ret ;
}

static int _recv_body(dd_http* http) 
{
    const char* lenVal    = dd_http_get_resp_header(http, "Content-Length") ;
    const char* chunedVal = dd_http_get_resp_header(http, "Transfer-Encoding") ;
    int         isChunked = 0 ;

    if (http->reqType == EHttpHead || memcmp(http->respCode, "204", 3) == 0)
        return 0 ;
    
    isChunked = (chunedVal != NULL && strcmp(chunedVal, "chunked") == 0) ;

    if ((lenVal == NULL || strcmp(lenVal, "0") == 0) && !isChunked) 
        return 0 ;

    if (isChunked)
        return _recv_chunked_body(http) ;
    
    return _recv_normal_body(http) ;
}

static void _free_header(void* headPtr) 
{
    dd_http_header* header = (dd_http_header*)headPtr ;

    __DD_ASSERT_DEFENSE(headPtr, return) ;
    dd_destroy(header->key) ;
    dd_destroy(header->value) ;
    dd_destroy(header) ;
}

static void _http_reset(dd_http* http) 
{
    dd_destroy(http->host) ;
    dd_destroy(http->address) ;
    dd_destroy(http->respCode) ;
    
    http->recvBufLen = 0 ;

    if (http->reqHeaders) 
        dd_vector_reset(http->reqHeaders, _free_header) ;

    if (http->respHeaders) 
        dd_vector_destroy(http->respHeaders, _free_header) ;

    if (http->postDataFun) 
        http->postDataFun->reset(http->postDataFun) ;
    
    if (http->zip)
        dd_zip_reset(http->zip) ;
}

static int _http_work(dd_http* http, const DD_UTF8* url, DD_UINT port, const dd_http_range* range) 
{
    int     ret = -1 ;
    DD_UINT ip = 0 ;
    
    if (!http->buf)
        http->buf = dd_malloc(__HTTP_BUF_LEN + 1) ; /* extract 1 byte for the zero terminator of string */
    __DD_ASSERT_DEFENSE(http->buf, goto _end) ;
    
    if ((ret = _parse_url(http, url)) < 0) 
        goto _end ;
    
    if ((ret = _create_headers(http, range)) < 0)
        goto _end ;

    dd_log_trace("start to do %s http request from server url: %s, port: %d", _reqString[http->reqType], url, port) ; 
    
    if (http->callback.start && ((ret = http->callback.start(http)) < 0)) {
        dd_log_error("got error: %d in http start function", ret) ;
        goto _end ;
    }
    
    __HTTP_CHECK_CANCEL(http, goto _end) ;

    if (!http->tcp) {
        /* get host ip */
        if (sd_get_host_ip(http->host, &ip) < 0) {
            ret = EHttpGetHostIpError ;
            goto _end ;
        }

        __HTTP_CHECK_CANCEL(http, goto _end) ;

        /* open tcp socket and connect */
        http->tcp = sd_tcp_open(1, 1) ;
        if (http->tcp == NULL) {
            ret = EHttpOpenTcpError ;
            goto _end ;
        }

        __HTTP_CHECK_CANCEL(http, goto _end) ;

        if (sd_tcp_connect(http->tcp, ip, port) < 0) {
            ret = EHttpConnectError ;
            goto _end ;
        }
    }

    __HTTP_CHECK_CANCEL(http, goto _end) ;
    
    if (_send_header(http) < 0) {
        ret = EHttpSendHeaderFailed ;
        goto _end ;
    }

    __HTTP_CHECK_CANCEL(http, goto _end) ;

    if (_send_post_body(http) < 0) {
        ret = EHttpSendBodyFailed ;
        goto _end ;
    }
    
    __HTTP_CHECK_CANCEL(http, goto _end) ;

    if (_recv_header(http) < 0) {
        ret = EHttpRecvBodyFailed ;
        goto _end ;
    }

    __HTTP_CHECK_CANCEL(http, goto _end) ;

    if (memcmp(http->respCode, "301", 3) == 0 && memcmp(http->respCode, "302", 3) == 0) {
        const char* location = dd_http_get_resp_header(http, "Location") ;
        if (location == NULL) {
            ret = EHttpRecvHeaderFailed ;
            dd_log_error("invalid http header for status code is %s, but did not get the location from header", http->respCode) ;
            goto _end ;
        }
        
        _http_reset(http) ;    
        
        if (http->tcp) {
            sd_tcp_close(http->tcp) ;
            http->tcp = NULL ;
        }

        ret = _http_work(http, (const DD_UTF8*)location, port, range) ;
    }

    if (http->callback.process_header && ((ret = http->callback.process_header(http)) < 0)) {
        dd_log_error("got error: %d in http process header call back function", ret) ;
        goto _end ;
    }

    __HTTP_CHECK_CANCEL(http, goto _end) ;
    
    if (_recv_body(http) < 0) {
        ret = EHttpRecvBodyFailed ;
        goto _end ;
    }

    ret = 0 ;

    _end:
    
    if (ret < 0) 
        dd_log_error("error to do the http work for the url: %s, port: %d, errorCode: %d", url, port, ret) ;

    return ret ;
}

static void _http_destroy(dd_http* http) 
{
    __DD_ASSERT_DEFENSE(http, return) ;
    
    _http_reset(http) ;

    if (http->tcp) {
        sd_tcp_close(http->tcp) ;
        http->tcp = NULL ;
    }
    
    if (http->postDataFun) 
        http->postDataFun->destroy(http->postDataFun) ;
    
    dd_destroy(http->buf) ;
    dd_destroy(http->zip) ;
    dd_destroy(http->unzipBuf) ;
    dd_free(http) ;
}
 

int dd_http_get(const dd_http_req_get* req) 
{
    dd_http* http = dd_malloc(sizeof(dd_http)) ;
    int      ret  = -1 ;
    
    __DD_CHECK_MEM(http, sizeof(dd_http), goto _end) ;
    
    http->reqType     = EHttpGet ;
    http->postDataFun = NULL ;
    
    
    http->callback = req->callback;
    
    if (req->rangeVector) {
        int i = 0 ;
        for (; i < dd_vector_count(req->rangeVector); i++) {
            ret = _http_work(http, req->url, req->port, (dd_http_range*)dd_vector_get(req->rangeVector, i)) ;
            if (ret < 0)  break ;
            
            _http_reset(http) ;
        }
    } else {
        ret = _http_work(http, req->url, req->port, NULL) ;
    }
    
    _end:
    if (http->callback.finish)
        http->callback.finish(http, ret) ;
    _http_destroy(http) ;
    return ret ;
}

int dd_http_post_buffer(const dd_http_req_post_buf* req) 
{
    dd_http* http = dd_malloc(sizeof(dd_http)) ;
    int      ret  = -1 ;

    __DD_CHECK_MEM(http, sizeof(dd_http), goto _end) ;

    http->reqType     = EHttpPost ;
    
    http->postDataFun = _create_post_buffer(req->buf, req->bufLen) ;
    if (!http->postDataFun)
        goto _end;

    http->callback = req->callback;

    ret = _http_work(http, req->url, req->port, NULL) ;
    
    _end:
    if (http->callback.finish)
        http->callback.finish(http, ret >= 0) ;
    _http_destroy(http) ;
    return ret ;
}

int dd_http_post_file(const dd_http_req_post_file* req) 
{
    dd_http* http = dd_malloc(sizeof(dd_http)) ;
    int      ret  = -1 ;

    __DD_CHECK_MEM(http, sizeof(dd_http), goto _end) ;

    http->reqType     = EHttpPost ;

    http->postDataFun = _create_post_file(req->fileName) ;
    if (!http->postDataFun)
        goto _end;

    http->callback = req->callback;

    ret = _http_work(http, req->url, req->port, NULL) ;
    
    _end:
    if (http->callback.finish)
        http->callback.finish(http, ret >= 0) ;
    _http_destroy(http) ;
    return ret ;
}

int dd_http_head(const dd_http_req_head* req) 
{
    dd_http* http = dd_malloc(sizeof(dd_http)) ;
    int      ret  = -1 ;

    __DD_CHECK_MEM(http, sizeof(dd_http), goto _end) ;

    http->reqType     = EHttpHead ;
    http->postDataFun = NULL ;

    http->callback = req->callback;
    
    ret = _http_work(http, req->url, req->port, NULL) ;

_end:
    if (http->callback.finish)
        http->callback.finish(http, ret) ;
    _http_destroy(http) ;
    return ret ;
}

const char* dd_http_resp_code(const dd_http* http) 
{
    __DD_ASSERT_DEFENSE(http, return NULL) ;

    return http->respCode ;
}

static int _header_cmp(const void* key, const void* data) 
{
    const dd_http_header* header = (const dd_http_header*)data ;
    return dd_strcasecmp((const char*)key, header->key) ;
}

static const char* _get_header_from(const dd_vector* vec, const char* key) 
{
    int             index = -1 ;
    dd_http_header* header = NULL ;

    index = dd_vector_find_by_key(vec, key, _header_cmp, 0) ;
    if (index == -1)  return NULL ;

    header = (dd_http_header*)dd_vector_get(vec, index) ;
    __DD_ASSERT_DEFENSE(header, return NULL) ;

    return header->value ;
}

const char* dd_http_get_req_header(const dd_http* http, const char* key) 
{
    __DD_ASSERT_DEFENSE(http && http->reqHeaders && key, return NULL) ;
    return _get_header_from(http->reqHeaders, key) ;
}

const char* dd_http_get_resp_header(const dd_http* http, const char* key) 
{
    __DD_ASSERT_DEFENSE(http && http->respHeaders && key, return NULL) ;
    return _get_header_from(http->respHeaders, key) ;
}

void dd_http_add_req_header(dd_http* http, const char* key, const char* val) 
{
    __DD_ASSERT_DEFENSE(http && http->reqHeaders, return) ;
    __DD_ASSERT_DEFENSE(key && val, return) ;

    _add_header(http->reqHeaders, key, val) ;
}

/* END sd_file */
